/*
 * Copyright (C) 2004 NNL Technology AB
 * Visit www.infonode.net for information about InfoNode(R) 
 * products and how to contact NNL Technology AB.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
 * MA 02111-1307, USA.
 */

// $Id: ShadowPainter.java,v 1.9 2005/12/04 13:46:05 jesper Exp $
package net.infonode.tabbedpanel.internal;

import net.infonode.gui.ComponentUtil;
import net.infonode.gui.GraphicsUtil;
import net.infonode.gui.UIManagerUtil;
import net.infonode.util.ColorUtil;
import net.infonode.util.Direction;

import javax.swing.*;
import java.awt.*;

/**
 * @author $Author: jesper $
 * @version $Revision: 1.9 $
 * @since ITP 1.1.0
 */
public class ShadowPainter {
	private Color panelBackgroundColor;
	private Color tabBackgroundColor;
	private Component component;
	private JComponent componentsPanel;
	private JComponent highlightedTab;
	private JComponent contentPanel;
	private JComponent tabAreaComponentsPanel;
	private JComponent tabAreaContainer;
	private JComponent tabBox;
	private Direction tabOrientation;
	private boolean paintTabAreaShadow;
	private int shadowSize;
	private int shadowBlendSize;
	private Color shadowColor;
	private float shadowStrength;
	private boolean highlightedTabIsLast;

	public ShadowPainter(Component component, JComponent componentsPanel, JComponent highlightedTab, JComponent contentPanel,
			JComponent tabAreaComponentsPanel, JComponent tabAreaContainer, JComponent tabBox, Direction tabOrientation, boolean paintTabAreaShadow,
			int shadowSize, int shadowBlendSize, Color shadowColor, float shadowStrength, boolean highlightedTabIsLast) {
		this.component = component;
		this.componentsPanel = componentsPanel;
		this.highlightedTab = highlightedTab == null ? null : !highlightedTab.isVisible() || !tabAreaContainer.isVisible() ? null : highlightedTab;
		this.contentPanel = contentPanel;
		this.tabAreaComponentsPanel = tabAreaComponentsPanel;
		this.tabAreaContainer = tabAreaContainer;
		this.tabBox = tabBox;
		this.tabOrientation = tabOrientation;
		this.paintTabAreaShadow = paintTabAreaShadow && tabAreaContainer.isVisible();
		this.shadowSize = shadowSize;
		this.shadowBlendSize = shadowBlendSize;
		this.shadowColor = shadowColor;
		this.shadowStrength = shadowStrength;
		this.highlightedTabIsLast = highlightedTabIsLast;
	}

	public void paint(Graphics g) {
		panelBackgroundColor = ComponentUtil.getBackgroundColor(component);
		panelBackgroundColor = panelBackgroundColor == null ? UIManagerUtil.getColor("Panel.background", "control") : panelBackgroundColor;

		if (paintTabAreaShadow) {
			Rectangle bounds = SwingUtilities.calculateInnerArea(componentsPanel, new Rectangle());

			// Right shadow
			drawBottomRightEdgeShadow(g, bounds.y, bounds.x + bounds.width, bounds.height, true, panelBackgroundColor);

			// Bottom shadow
			drawBottomRightEdgeShadow(g, bounds.x, bounds.y + bounds.height, bounds.width, false, panelBackgroundColor);

			// Bottom right corner
			drawRightCornerShadow(g, bounds.x + bounds.width, bounds.y + bounds.height, false, panelBackgroundColor);
		} else {
			tabBackgroundColor = highlightedTab == null ? panelBackgroundColor : ComponentUtil.getBackgroundColor(highlightedTab.getParent());

			tabBackgroundColor = tabBackgroundColor == null ? panelBackgroundColor : tabBackgroundColor;

			Rectangle contentPanelBounds = contentPanel.getBounds();
			int len = 0;

			if (highlightedTab != null)
				len = paintHighlightedTabShadow(g, tabOrientation, contentPanelBounds);

			if (tabAreaComponentsPanel.isVisible()) {
				len = tabOrientation.isHorizontal() ? (tabAreaContainer.getInsets().bottom == 0 ? tabAreaComponentsPanel.getWidth() : 0) : (tabAreaContainer
						.getInsets().right == 0 ? tabAreaComponentsPanel.getHeight() : 0);
			}

			if (!tabAreaContainer.isVisible())
				len = 0;

			if (tabOrientation != Direction.RIGHT || highlightedTab == null)
				drawBottomRightEdgeShadow(g, contentPanelBounds.y - (tabOrientation == Direction.UP ? len : 0),
						contentPanelBounds.x + contentPanelBounds.width, contentPanelBounds.height + (!tabOrientation.isHorizontal() ? len : 0), true,
						highlightedTab == null ? null : panelBackgroundColor);

			if (tabOrientation != Direction.DOWN || highlightedTab == null)
				drawBottomRightEdgeShadow(g, contentPanelBounds.x - (tabOrientation == Direction.LEFT ? len : 0), contentPanelBounds.y
						+ contentPanelBounds.height, contentPanelBounds.width + (tabOrientation.isHorizontal() ? len : 0), false, highlightedTab == null ? null
						: panelBackgroundColor);

			drawRightCornerShadow(g, contentPanelBounds.x + contentPanelBounds.width + (tabOrientation == Direction.RIGHT ? len : 0), contentPanelBounds.y
					+ contentPanelBounds.height + (tabOrientation == Direction.DOWN ? len : 0), false, panelBackgroundColor);
		}

	}

	private int paintHighlightedTabShadow(Graphics g, Direction tabOrientation, Rectangle contentPanelBounds) {
		Point p = SwingUtilities.convertPoint(highlightedTab.getParent(), highlightedTab.getLocation(), component);

		Dimension tabsSize = tabBox.getSize();
		Rectangle bounds = tabAreaComponentsPanel.isVisible() ? SwingUtilities.convertRectangle(tabAreaComponentsPanel.getParent(),
				tabAreaComponentsPanel.getBounds(), component) : new Rectangle(contentPanelBounds.x + contentPanelBounds.width, contentPanelBounds.y
				+ contentPanelBounds.height, 0, 0);
		Point tabsPos = SwingUtilities.convertPoint(tabBox, 0, 0, component);

		// Set tab clip
		int width = (tabOrientation.isHorizontal() ? 0 : tabsPos.x) + tabsSize.width;
		int height = (tabOrientation.isHorizontal() ? tabsPos.y : 0) + tabsSize.height;

		if (tabOrientation == Direction.DOWN)
			drawBottomRightTabShadow(g, contentPanelBounds.x, contentPanelBounds.y + contentPanelBounds.height, contentPanelBounds.width, p.x,
					highlightedTab.getWidth(), highlightedTab.getHeight(), bounds.x, bounds.width, bounds.height, false, highlightedTabIsLast);
		else if (tabOrientation == Direction.RIGHT)
			drawBottomRightTabShadow(g, contentPanelBounds.y, contentPanelBounds.x + contentPanelBounds.width, contentPanelBounds.height, p.y,
					highlightedTab.getHeight(), highlightedTab.getWidth(), bounds.y, bounds.height, bounds.width, true, highlightedTabIsLast);
		else if (tabOrientation == Direction.UP) {
			drawTopLeftTabShadow(g, p.x + highlightedTab.getWidth(), p.y, highlightedTab.getHeight(), bounds, contentPanelBounds.width, false,
					highlightedTabIsLast);
		} else
			drawTopLeftTabShadow(g, p.y + highlightedTab.getHeight(), p.x, highlightedTab.getWidth(), flipRectangle(bounds), contentPanelBounds.height, true,
					highlightedTabIsLast);

		if (highlightedTabIsLast) {
			return tabOrientation.isHorizontal() ? (p.y + highlightedTab.getHeight() >= contentPanelBounds.height && contentPanelBounds.height == height ? highlightedTab
					.getWidth() : 0)
					: (p.x + highlightedTab.getWidth() >= contentPanelBounds.width && contentPanelBounds.width == width ? highlightedTab.getHeight() : 0);
		} else
			return 0;
	}

	private static Rectangle flipRectangle(Rectangle bounds) {
		return new Rectangle(bounds.y, bounds.x, bounds.height, bounds.width);
	}

	private static Rectangle createRectangle(int x, int y, int width, int height, boolean flip) {
		return flip ? new Rectangle(y, x, height, width) : new Rectangle(x, y, width, height);
	}

	private void drawTopLeftTabShadow(Graphics g, int x, int y, int height, Rectangle componentsBounds, int totalWidth, boolean flip, boolean isLast) {
		boolean connected = x + shadowSize > componentsBounds.x;

		if (!connected || y + shadowSize + shadowBlendSize <= componentsBounds.y) {
			drawLeftCornerShadow(g, y, Math.min(x, componentsBounds.x), !flip, isLast ? tabBackgroundColor : null);
			drawEdgeShadow(g, y, connected ? componentsBounds.y : y + height, Math.min(x, componentsBounds.x), false, !flip, isLast ? tabBackgroundColor : null);
		}

		int endX = componentsBounds.x + componentsBounds.width;

		if (endX < totalWidth) {
			drawLeftCornerShadow(g, componentsBounds.y, endX, !flip, tabBackgroundColor);
			drawEdgeShadow(g, componentsBounds.y, componentsBounds.y + componentsBounds.height, endX, false, !flip, tabBackgroundColor);
		}
	}

	private void drawBottomRightTabShadow(Graphics g, int x, int y, int width, int tabX, int tabWidth, int tabHeight, int componentsX, int componentsWidth,
			int componentsHeight, boolean flip, boolean isLast) {
		Shape oldClipRect = g.getClip();

		Rectangle clipRect = createRectangle(x, y, Math.min(tabX, componentsX) - x, 1000000, flip);
		g.clipRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);

		drawLeftCornerShadow(g, x, y, flip, null);
		drawEdgeShadow(g, x, tabX, y, false, flip, null);

		boolean connected = tabX < componentsX && tabX + tabWidth >= componentsX;
		g.setClip(oldClipRect);

		if (!connected) {
			Rectangle clipRectTemp = createRectangle(x, y, componentsX - x, 1000000, flip);
			g.clipRect(clipRectTemp.x, clipRectTemp.y, clipRectTemp.width, clipRectTemp.height);
		}

		if (tabX < componentsX) {
			int endX = connected && tabHeight == componentsHeight ? componentsX + componentsWidth : Math.min(componentsX, tabX + tabWidth);

			if (tabX + shadowSize < endX)
				drawLeftCornerShadow(g, tabX, y + tabHeight, flip, panelBackgroundColor);

			drawEdgeShadow(g, tabX, endX, y + tabHeight, false, flip, panelBackgroundColor);

			if (endX < x + width && (!connected || componentsHeight < tabHeight)) {
				drawRightCornerShadow(g, endX, y + tabHeight, flip, panelBackgroundColor);
				drawEdgeShadow(g, y + (connected ? componentsHeight : 0), y + tabHeight, endX, true, !flip, isLast ? tabBackgroundColor : null);
			}
		}

		if (!connected) {
			drawEdgeShadow(g, tabX + tabWidth, componentsX, y, true, flip, isLast ? tabBackgroundColor : null);
			g.setClip(oldClipRect);
		}

		if (componentsHeight > 0 && (!connected || tabHeight != componentsHeight)) {
			if (!connected || componentsHeight > tabHeight) {
				drawLeftCornerShadow(g, componentsX, y + componentsHeight, flip, panelBackgroundColor);
				drawEdgeShadow(g, componentsX, componentsX + componentsWidth, y + componentsHeight, false, flip, panelBackgroundColor);
			} else
				drawEdgeShadow(g, componentsX, componentsX + componentsWidth, y + componentsHeight, true, flip, panelBackgroundColor);
		}

		if (componentsX + componentsWidth < x + width) {
			drawRightCornerShadow(g, componentsX + componentsWidth, y + componentsHeight, flip, panelBackgroundColor);
			drawEdgeShadow(g, y, y + componentsHeight, componentsX + componentsWidth, true, !flip, panelBackgroundColor);
			drawEdgeShadow(g, componentsX + componentsWidth, x + width, y, true, flip, panelBackgroundColor);
		}
	}

	private void drawBottomRightEdgeShadow(Graphics g, int x, int y, int width, boolean flip, Color backgroundColor) {
		drawLeftCornerShadow(g, x, y, flip, backgroundColor);
		drawEdgeShadow(g, x, x + width, y, false, flip, backgroundColor);
	}

	private void drawLeftCornerShadow(Graphics g, int x, int y, boolean upper, Color backgroundColor) {
		for (int i = 0; i < shadowBlendSize; i++) {
			g.setColor(getShadowBlendColor(i, backgroundColor));
			int x1 = x + shadowSize + shadowBlendSize - 1 - i;
			int y1 = y + shadowSize - shadowBlendSize;

			if (y1 > y)
				drawLine(g, x1, y, x1, y1 - 1, upper);

			drawLine(g, x1, y1, x + shadowSize + shadowBlendSize - 1, y + shadowSize - shadowBlendSize + i, upper);
		}
	}

	private void drawRightCornerShadow(Graphics g, int x, int y, boolean flip, Color backgroundColor) {
		g.setColor(getShadowColor(backgroundColor));

		for (int i = 0; i < shadowSize - shadowBlendSize; i++) {
			drawLine(g, x + i, y, x + i, y + shadowSize - shadowBlendSize, flip);
		}

		for (int i = 0; i < shadowBlendSize; i++) {
			g.setColor(getShadowBlendColor(i, backgroundColor));
			int d = shadowSize - shadowBlendSize + i;
			drawLine(g, x + d, y, x + d, y + shadowSize - shadowBlendSize, flip);
			drawLine(g, x, y + d, x + shadowSize - shadowBlendSize, y + d, flip);
			drawLine(g, x + d, y + shadowSize - shadowBlendSize, x + shadowSize - shadowBlendSize, y + d, flip);
		}
	}

	private void drawEdgeShadow(Graphics g, int startX, int endX, int y, boolean cornerStart, boolean vertical, Color backgroundColor) {
		if (startX + (cornerStart ? 0 : shadowSize + shadowBlendSize) >= endX)
			return;

		g.setColor(getShadowColor(backgroundColor));

		for (int i = 0; i < shadowSize - shadowBlendSize; i++) {
			drawLine(g, startX + (cornerStart ? i + (vertical ? 1 : 0) : shadowSize + shadowBlendSize), y + i, endX - 1, y + i, vertical);
		}

		for (int i = 0; i < shadowBlendSize; i++) {
			g.setColor(getShadowBlendColor(i, backgroundColor));
			int d = shadowSize - shadowBlendSize + i;
			drawLine(g, startX + (cornerStart ? d + (vertical ? 1 : 0) : shadowSize + shadowBlendSize), y + d, endX - 1, y + d, vertical);
		}
	}

	private static void drawLine(Graphics g, int x1, int y1, int x2, int y2, boolean flip) {
		if (flip)
			GraphicsUtil.drawOptimizedLine(g, y1, x1, y2, x2);
		else
			GraphicsUtil.drawOptimizedLine(g, x1, y1, x2, y2);
	}

	private Color getShadowBlendColor(int offset, Color backgroundColor) {
		return backgroundColor == null ? new Color(shadowColor.getRed(), shadowColor.getGreen(), shadowColor.getBlue(), (int) (255F * shadowStrength
				* (shadowBlendSize - offset) / (shadowBlendSize + 1))) : ColorUtil.blend(backgroundColor, shadowColor, shadowStrength
				* (shadowBlendSize - offset) / (shadowBlendSize + 1));
	}

	private Color getShadowColor(Color backgroundColor) {
		return backgroundColor == null ? new Color(shadowColor.getRed(), shadowColor.getGreen(), shadowColor.getBlue(), (int) (255F * shadowStrength))
				: ColorUtil.blend(backgroundColor, shadowColor, shadowStrength);
	}
}
